#  This is part of Scopia Visual Framework
#
#  This module contains class GlContext which is the base class
#  for any OpenGL application in this framework
#
#  Copyright (C) 2008  Scopia Visual Interfaces Systems 
#                      http://www.scopia.es
#
#  author: Alejandro Conty
#  contributors:
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import object3d
import sys
import Image
import time
from OpenGL.GL import *
from OpenGL.GLU import *
import OpenGL.GL.EXT.blend_color as blend_color

hasBlendColor = False

def setDefaultBlending():
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

class GlContext( object ):
    """Base class for opengl applications. Handles common GL setup
       and has helper methods for setting up textures, id's and more"""
    def __init__( self, w, h):
        "Init method, takes w, h as the width and height of the viewport"
        self.width = w
        self.height = h
        glCullFace( GL_BACK )
        glEnable( GL_CULL_FACE )
        self.depthEnabled = False
        self.depthTest( True )
        self.blending( False)
        self.ghosting( False)
        self.smoothing( True )
        self.antialias( True )
        global hasBlendColor
        hasBlendColor = blend_color.glInitBlendColorEXT()
        #if hasBlendColor: print 'Using color blending extension'
        #self.nextList = 2
        self.nextselid = 1
        self.selid = {}
        self.staticselid = {}
        self.onList = False
        glNewList(1, GL_COMPILE)
        glEndList()
        self.textures = {}
        self.objects = {}
        self.font = None
        self.capture = False
        self.captureNumber = 1
        self.captureImage = None
        self.motionObject = None
        self.timers = []
        self.lastTimerTime = time.time()

    def setViewport(self, x, y, w, h):
        glViewport(x, y, w, h)

    def startCallback(self, obj):
        """Tags the next geometry to triger a callback when clicked. there
           is a potential memory leak here cause "selid" has no guarantee
           to be cleared when that geometry no longer exists"""
        oid = id(obj) % 1000000000
        if not obj in self.selid: self.selid[ oid ] = obj
        glPushName( oid )
    
    def endCallback(self):
        "End of the geometry associated with the previous callback"
        glPopName()

    def freeCallbacks(self):
        "TODO: this should be called from time to time or do something to solve the leak"
        self.selid = {}

    #def assocObject(self, id):
    #    try:
    #        obj = self.selid[id]
    #    except KeyError, ex:
    #        obj = self.staticselid[id]
    #    return obj

    def select(self, x, y):
        "Given a point in the screen returns a list of callable objects under it"
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        result = list(glSelectWithCallback(x, y, self.selectDisplay, 5, 5, 1024))
        if not result or len(result) == 0: return []
        result.sort( lambda a, b: cmp(a[0], b[0]) )
        names = result[0][2]
        return [ self.selid[i] for i in names ]

    def getListId(self):
        "Returns a new free display list id"
        return glGenLists(1)

    def newList(self):
        "Starts compilation of a display list, everything gets recored until you call endList"
        id = self.getListId()
        glNewList(id, GL_COMPILE)
        self.onList = True
        return id

    def endList(self):
        "Finish the compilation of a display list"
        glEndList()
        self.onList = False

    def callList(self, id):
        "Calls an already compiled display list"
        glCallList( id )

    def deleteList(self, id):
        "Deletes an existing display list"
        glDeleteLists( id, 1 )

    def objectFile( self, name ):
        """Inserts the geometry of an object from a file using our private
           format. Keeps a cache so you can call it many times and it will
           load the file only once"""
        try:
            id = self.objects[ name ]
        except KeyError, ex:
            obj = object3d.object3d_t( name )
            if obj.texture: self.loadTexture( obj.texture )
            id = self.newList()
            obj.draw(self)
            self.endList()
            self.objects[ name ] = id
        self.callList( id )
            
    def depthTest( self, v = None):
        """If no params are given returns a boolean with the current
           state. Otherwise it enables/disables depth test"""
        ret = self.depthEnabled
        if v is None: return ret
        if v:
            glEnable(GL_DEPTH_TEST)
            self.depthEnabled = True
        else:
            glDisable(GL_DEPTH_TEST)
            self.depthEnabled = False
        return ret
    
    def ghosting(self, v = None):
        """If no params are given returns a boolean with the current
           state. Otherwise it enables/disables our special "ghost"
           blending mode"""
        if v is None: return self.ghostingEnabled
        if v:
            glBlendFunc(blend_color.GL_CONSTANT_ALPHA_EXT, GL_ONE)
            self.ghostingEnabled = True
        else:
            setDefaultBlending()
            self.ghostingEnabled = False
    
    def blending(self, v = None):
        """If no params are given returns a boolean with the current
           state. Otherwise it enables/disables standar blending mode"""
        if v is None: return self.blendingEnabled
        if v:
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            self.blendingEnabled = True
        else:
            setDefaultBlending()
            self.blendingEnabled = False
    
    def outBlending(self, v = None):
        """If no params are given returns a boolean with the current
           state. Otherwise it enables/disables blending using a constant
           value for alpha. Useful for blending display lists."""
        if v is None: return self.blendingEnabled
        if v:
            glBlendFunc(blend_color.GL_CONSTANT_ALPHA_EXT, blend_color.GL_ONE_MINUS_CONSTANT_ALPHA_EXT)
            self.blendingEnabled = True
        else:
            setDefaultBlending()
            self.blendingEnabled = False
    
    def smoothing( self, v = None):
        """If no params are given returns a boolean with the current
           state. Otherwise it enables/disables normal smoothing."""
        if v is None: return self.smoothingEnabled
        if v:
            glShadeModel(GL_SMOOTH)
            self.smoothingEnabled = True
        else:
            glShadeModel(GL_FLAT)
            self.smoothingEnabled = False
    
    def antialias( self, v = None):
        """If no params are given returns a boolean with the current
           state. Otherwise it enables/disables anitaliasing for lines
           and points. Needs blending enabled."""
        if v is None: return self.antialiasEnabled
        if v:
            glEnable( GL_LINE_SMOOTH )
            glEnable( GL_POINT_SMOOTH )
            self.antialiasEnabled = True
        else:
            glDisable( GL_LINE_SMOOTH )
            glDisable( GL_POINT_SMOOTH )
            self.antialiasEnabled = False

    def disolve(self, alpha):
        "Changes the constant blending alpha value, see outBlending"
        global hasBlendColor
        if not hasBlendColor: return 1.0
        blend_color.glBlendColorEXT(alpha, alpha, alpha, alpha)
        return 1.0

    def _display(self):
        "Intermediate display function. Calls display after basic cleaning is done"
        glClearColor(1.0, 1.0, 1.0, 0.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        self.display()
        glFlush()
        if self.capture: self.captureFrame()

    def captureFrame(self):
        "Saves the current frame buffer to an image file (jpg) using a sequence number (self.captureNumber)"
        glReadBuffer ( GL_BACK )
        pixels = glReadPixels (0, 0, self.width, self.height, GL_RGB, GL_UNSIGNED_BYTE)
        img = Image.frombuffer( "RGB", (self.width, self.height), pixels, 'raw', 'RGB', 0, 1)
        if self.captureImage:
            img = Image.blend(self.captureImage, img, 0.5)
            img = img.transpose(Image.FLIP_TOP_BOTTOM) 
            img.save( ('/tmp/cap_%.5d.jpg'%(self.captureNumber)) )
            self.captureImage = None
        else: self.captureImage = img
        self.captureNumber += 1
    
    def selectDisplay(self):
        "Relaxed display function for doing selection(picking)"
        glClearColor(0.0, 0.0, 0.0, 0.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        self.display()
        
        glFlush()

    def display(self):
        "Actual display function implemented by derivate classes"
        pass
    
    def mouse( self, buttons, down, x, y):
        """Mouse callback function implemented by derivate classes
                
                buttons     A tuple of five boolean values for the activated button 
                            (left,middle,right,wheelup, wheeldown)
                down        Boolean saying if button is down or up clicked
                x,y         Position of the cursos in the window in pixel coordinates"""
        button = list(buttons).index(True)
        if down and not self.motionObject:
            sel = self.select( x, y )
            for handler in sel:
                if handler.button == button:
                    handler.down(x, y)
                    if button < 3:
                        self.motionObject = handler
                        self.oldmouse = ( x, y )
                    self.forceDisplay()
                    break
        if not down and self.motionObject and self.motionObject.button == button:
            self.motionObject.up(x, y)
            self.motionObject = None
            self.forceDisplay()
        if not down and button >= 3:
            sel = self.select( x, y )
            for handler in sel:
                if handler.button == button:
                    handler.up(x, y)
                    self.forceDisplay()
                    break

    def motion( self, x, y):
        "Mouse motion function when some button is down"
        if self.motionObject:
            unitsperpix = 2.0 / self.height
            self.motionObject.motion( self.oldmouse, ( x, y ), unitsperpix )
            self.forceDisplay()

    def passiveMotion( self, x, y):
        "Mouse motion function when no button is pressed"
        pass
    
    def addTimer(self, func): self.timers.append( func )

    def timer(self):
        """Timer func called 100 times a second (not guaranteed).
           In the timer function we just iterate timers and call them. Those
           that return a false value are removed from the list."""
        t = time.time()
        elapsed = t - self.lastTimerTime
        self.lastTimerTime = t
        self.time = 0
        if elapsed > 0.1: elapsed = 0.1
        if self.capture: elapsed = 0.02
        erase = []
        timers = [ f for f in self.timers ]
        for func in timers:
            if not func(elapsed): erase.append(func)
        for func in erase:
            idx = self.timers.index(func)
            self.timers = self.timers[:idx] + self.timers[idx+1:]
        if timers: self.forceDisplay()
        return elapsed

    def keyboard( self, key, x, y ):
        "Keyboard function"
        pass

    # wrappers of basic openGL functions
    vertex = glVertex3f
    setColor = glColor3f
    setColorA = glColor4f
    def translate( self, v ): glTranslatef( v[0], v[1], v[2] )
    def rotate( self, angle, v ): glRotatef( angle, v[0], v[1], v[2] )
    def scale( self, v ): glScalef( v[0], v[1], v[2] )
    def startQuads( self ): glBegin( GL_QUADS )
    def endQuads( self ): glEnd()
    def startTriangles( self ): glBegin( GL_TRIANGLES )
    def endTriangles( self ): glEnd()
    def startLines( self ): glBegin( GL_LINES )
    def endLines( self ): glEnd()
    def startLineStrip( self ): glBegin( GL_LINE_STRIP )
    def endLineStrip( self ): glEnd()
    def pointSize( self, s ): glPointSize( s )
    def startPoints( self ): glBegin( GL_POINTS )
    def endPoints( self ): glEnd()
    def texCoord( self, v ): glTexCoord2d(v[0], v[1]);
    def saveTransform( self ): glPushMatrix()
    def loadTransform( self ): glPopMatrix()

    def lineWidth(self, w): glLineWidth(w)

    def glowLine(self, a, b, color):
        "Draws a line between two point with a glow effect. Needs ghosting mode enabled."
        for i, alpha in enumerate( (1.0, 0.3, 0.1) ):
            glLineWidth(i*3+1)
            #glColor4f( color[0], color[1], color[2], color[3]*alpha)
            glColor3f( color[0]*alpha, color[1]*alpha, color[2]*alpha)
            glBegin(GL_LINES)
            glVertex3f(a[0], a[1], a[2])
            glVertex3f(b[0], b[1], b[2])
            glEnd()

    #def type2D(self, pos, text):
    #    if not self.font: 
    #        self.font = glfont.glFont_t(30)
    #        self.font.recalculate(self.width, self.height, 2)
    #    self.font.type( pos, text )

    #def startText(self):
    #    if not self.font: 
    #        self.font = glfont.glFont_t(30)
    #        self.font.recalculate(self.width, self.height, 2)
    #    self.font.setTextTexture()

    #def putCharQuad(self, points, char): self.font.putCharQuad(points, char)

    #def endText(self):
    #    self.font.unsetTextTexture()

    #def textWidth(self, text):
    #    if not self.font: 
    #        self.font = glfont.glFont_t(30)
    #        self.font.recalculate(self.width, self.height, 2)
    #    return self.font.textWidth( text )
    #def textHeight(self, text):
    #    if not self.font: 
    #        self.font = glfont.glFont_t(30)
    #        self.font.recalculate(self.width, self.height, 2)
    #    return self.font.textHeight( text )
    
    def putView(  self, vfrom, vto, vup, angle, clips, clipe, fog = 0 ):
        """Sets up a camera projection

                vfrom       Camera position
                vto         View direction
                vup         Camera up vector
                angle       FOV angle
                clips       Start clipping distance
                clipe       End clipping distance
                fog         If > 0 it adds a fog effect starting at that distance 
                            to the clip end"""
        ratio = float(self.width) / float(self.height)
        glMatrixMode( GL_PROJECTION )
        glPushMatrix()
        gluPerspective( angle, ratio, clips, clipe )
        glMatrixMode( GL_MODELVIEW )
        glPushMatrix()
        gluLookAt( vfrom[0], vfrom[1], vfrom[2], vto[0], vto[1], vto[2], vup[0], vup[1], vup[2] )
        if fog:
            glFogf(GL_FOG_START,  fog)
            glFogf (GL_FOG_END,    clipe)
            glFogfv(GL_FOG_COLOR,  (1, 1, 1, 1) )
            glFogi (GL_FOG_MODE,   GL_LINEAR)
            #glFogf (GL_FOG_DENSITY, 0.03)
            glEnable(GL_FOG)
        else: glDisable(GL_FOG)

    def restoreView( self ):
        "Resets the projection to the previous one"
        glMatrixMode( GL_MODELVIEW )
        glPopMatrix()
        glMatrixMode( GL_PROJECTION )
        glPopMatrix()
    
    def putOrtho(self, minx, maxx, miny, maxy):
        "Sets up a standar ortho projection in the [-1, 1]x[-1, 1] box"
        glMatrixMode( GL_PROJECTION )
        glPushMatrix()
        #glPopMatrix()
        glOrtho( minx, maxx, miny, maxy, -1, 1 )
        glMatrixMode( GL_MODELVIEW )
        #glPopMatrix()
        glPushMatrix()
    
    def loadTextureImg(self, img):
        "Loads a texture into card's memory from a PIL image and returns the id"
        mode = GL_RGBA
        try: data = img.tostring('raw', 'RGBA')
        except:
            data = img.tostring('raw', 'RGB')
            mode = GL_RGB
        id = glGenTextures(1)
        glBindTexture( GL_TEXTURE_2D, id )
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
        #glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        #glTexImage2D(GL_TEXTURE_2D, 0, mode, img.size[0], img.size[1], 0, mode, GL_UNSIGNED_BYTE, data)
        gluBuild2DMipmaps(GL_TEXTURE_2D, mode, img.size[0], img.size[1], mode, GL_UNSIGNED_BYTE, data)
        return id

    def loadTexture(self, filename):
        "Loads a texture from a file and returns its id"
        print 'loading', filename
        img = Image.open( filename )
        id = self.loadTextureImg( img )
        self.textures[filename] = id
        return id
        #mode = GL_RGBA
        #try:
        #    data = img.tostring('raw', 'RGBA')
        #except:
        #    data = img.tostring('raw', 'RGB')
        #    mode = GL_RGB
        #id = glGenTextures(1)
        #glBindTexture( GL_TEXTURE_2D, self.textures[filename] )
        #glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
        ##glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        #glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
        #glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        #glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        #glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        ##glTexImage2D(GL_TEXTURE_2D, 0, mode, img.size[0], img.size[1], 0, mode, GL_UNSIGNED_BYTE, data)
        #gluBuild2DMipmaps(GL_TEXTURE_2D, mode, img.size[0], img.size[1], mode, GL_UNSIGNED_BYTE, data)
        #return id
    
    def startTexture(self, filename):
        """Starts using a texture from a file for the next geometry.
           It only loads it the first time so you can call it always this way."""
        glEnable( GL_TEXTURE_2D )
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
        if self.textures.has_key( filename ): glBindTexture( GL_TEXTURE_2D, self.textures[filename] )
        else: self.loadTexture( filename )
    
    def endTexture(self, filename):
        "Finish using this texture for geometry"
        glBindTexture( GL_TEXTURE_2D, 0)
        glDisable( GL_TEXTURE_2D )
    
    def startTextureId(self, id):
        "Enables a texture by its given ID"
        glEnable( GL_TEXTURE_2D )
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
        glBindTexture( GL_TEXTURE_2D, id )
    
    def endTextureId(self, id):
        "Disables a texture by its given ID"
        glBindTexture( GL_TEXTURE_2D, 0)
        glDisable( GL_TEXTURE_2D )

    def getTextureId(self, filename):
        "Returns the id for a texture in a file. Loads it if it is not present."
        try: id = self.textures[filename]
        except: id = self.loadTexture( filename )
        return id
    
    def eraseTexture(self, filename):
        "Deletes a texture from te card's memory using its original filename"
        try: id = self.textures[filename]
        except: return
        glDeleteTextures( [ id ] )
    
    def eraseTextureId(self, id):
        "Deletes a texture from te card's memory using its id"
        glDeleteTextures( [ id ] )


    def projectScreen( self, point ):
        """Projects a point to the screen using the current transform
           matrix and projection. Returns a tuple (x, y) with the coords."""
        vp = glGetIntegerv (GL_VIEWPORT)
        spoint = gluProject( point[0], point[1], point[2], glGetDoublev (GL_MODELVIEW_MATRIX),
                             glGetDoublev (GL_PROJECTION_MATRIX), vp)
        x = spoint[0] * 2.0 / float(vp[2]) - 1.0
        y = spoint[1] * 2.0 / float(vp[3]) - 1.0
        return ( x, y )
        
    def drawQuadArray( self, points, uvs=None, colors = None ):
        """Draws quads from the points in a numpy array
                
                points      numpy array(n,3) with the points
                uvs         numpy array(n,2) with the uv coords for the points (optional)
                colors      numpy array(n,3) with the colors for the points (optional)"""
        glEnableClientState(GL_VERTEX_ARRAY)
        if uvs != None: 
            glEnableClientState(GL_TEXTURE_COORD_ARRAY)
            glTexCoordPointerf( uvs )
        if colors != None: 
            glEnableClientState(GL_COLOR_ARRAY)
            glColorPointerf( colors )
        glVertexPointerf(points)
        glDrawArrays( GL_QUADS, 0, len(points) )
        glDisableClientState(GL_TEXTURE_COORD_ARRAY)
        glDisableClientState(GL_COLOR_ARRAY)
        glDisableClientState(GL_VERTEX_ARRAY)

    def drawTriangleFanArray( self, points, uvs=None ):
        """Draws a triangle fan (see opengl doc) from numpy arrays
                points      numpy array(n,3) with the points
                uvs         numpy array(n,2) with the uv coords for the points (optional)"""
        glEnableClientState(GL_VERTEX_ARRAY)
        if uvs != None: 
            glEnableClientState(GL_TEXTURE_COORD_ARRAY)
            glTexCoordPointerf( uvs )
        glVertexPointerf(points)
        glDrawArrays( GL_TRIANGLE_FAN, 0, len(points) )
        glDisableClientState(GL_TEXTURE_COORD_ARRAY)
        glDisableClientState(GL_VERTEX_ARRAY)
    
    def drawTriangleArray( self, points, uvs=None ):
        """Draws a triangles from numpy arrays
                points      numpy array(n,3) with the points
                uvs         numpy array(n,2) with the uv coords for the points (optional)"""
        glEnableClientState(GL_VERTEX_ARRAY)
        if uvs != None: 
            glEnableClientState(GL_TEXTURE_COORD_ARRAY)
            glTexCoordPointerf( uvs )
        glVertexPointerf(points)
        glDrawArrays( GL_TRIANGLES, 0, len(points) )
        glDisableClientState(GL_TEXTURE_COORD_ARRAY)
        glDisableClientState(GL_VERTEX_ARRAY)
    
    def drawLineArray( self, points, colors=None ):
        """Draws lines from the points from numpy arrays
                
                points      numpy array(n,3) with the points
                colors      numpy array(n,3) with the colors for the points (optional)"""
        glEnableClientState(GL_VERTEX_ARRAY)
        if colors != None: 
            glEnableClientState(GL_COLOR_ARRAY)
            glColorPointerf( colors )
        glVertexPointerf(points)
        glDrawArrays( GL_LINES, 0, len(points) )
        glDisableClientState(GL_COLOR_ARRAY)
        glDisableClientState(GL_VERTEX_ARRAY)

    def drawLineStripArray( self, points, colors=None, loop = False ):
        """Draws a line strip (see opengl doc) from the points from numpy arrays
                
                points      numpy array(n,3) with the points
                colors      numpy array(n,3) with the colors for the points (optional)"""
        glEnableClientState(GL_VERTEX_ARRAY)
        if colors != None: 
            glEnableClientState(GL_COLOR_ARRAY)
            glColorPointerf( colors )
        glVertexPointerf(points)
        if loop: glDrawArrays( GL_LINE_LOOP, 0, len(points) )
        else: glDrawArrays( GL_LINE_STRIP, 0, len(points) )
        glDisableClientState(GL_COLOR_ARRAY)
        glDisableClientState(GL_VERTEX_ARRAY)

def glSelectWithCallbackStolen(x, y, callback, xsize = 5, ysize = 5, buffer_size = 512):
    """This function used to be in pyopengl but it isn't anylonger so we implement it
       here. It is just a helper for selection (picking)

            x, y            Coordinates for picking
            callback        Display function to render geometry
            xsize,ysize     Square (pixel region) around cursor to look for geometry
            buffer_size     Selection buffer size for gathering results"""
    viewport = glGetIntegerv(GL_VIEWPORT)
    glSelectBuffer(buffer_size)
    glRenderMode(GL_SELECT)

    glInitNames()
    glMatrixMode(GL_PROJECTION)
    previousviewmatrix = glGetDoublev(GL_PROJECTION_MATRIX)
    glLoadIdentity()
    gluPickMatrix(x, viewport[3] - y, xsize, ysize, viewport)
    glMultMatrixd(previousviewmatrix)
    callback()
    glFlush()
    glMatrixMode(GL_PROJECTION)
    glLoadMatrixd(previousviewmatrix)

    return glRenderMode(GL_RENDER)

glSelectWithCallback = glSelectWithCallbackStolen
